package com.star.jersey.mybatisplus.base.web.exception;

import com.star.jersey.mybatisplus.base.core.contract.R;
import com.star.jersey.mybatisplus.base.core.contract.enums.ErrorCodeEnum;
import com.star.jersey.mybatisplus.base.core.contract.enums.ResultCode;
import com.star.jersey.mybatisplus.base.core.contract.exception.ServiceException;
import com.star.jersey.mybatisplus.base.core.contract.exception.ValidateException;
import com.star.jersey.mybatisplus.base.utils.JsonUtils;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 全局异常类
 * @author zhouhengzhe
 */
@RestControllerAdvice
public class BaseExceptionAdvice {
    private static final Logger log = LoggerFactory.getLogger(BaseExceptionAdvice.class);

    private static String getUUID() {
        return UUID.randomUUID().toString().substring(24);
    }

    public BaseExceptionAdvice() {
        log.info("### base init : BaseExceptionAdvice ###");
    }

    /**
     * 默认全局异常处理。
     *
     * @param e the e
     * @return ResultData
     */
    @ExceptionHandler(Exception.class)
    public R<String> error(HttpServletRequest request, Exception e) {

        RequestInfoDTO reqInfo = getRequestInfo(request, true);
        log.error("全局异常信息 ex={},req={}", e.getMessage() ,reqInfo,e);
        return R.fail(ErrorCodeEnum.REQUEST_INTERFACE_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler({BindException.class})
    public R<String> bindException(HttpServletRequest request, Model model, BindException e) {
        RequestInfoDTO reqInfo = getRequestInfo(request, true);
        List<String> errList = e.getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        log.error("参数检验错误，绑定异常，异常信息为 {} {} {}", reqInfo, errList, model);
        return R.fail(ErrorCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), StringUtils.join(errList, ","));
    }

    @ExceptionHandler({ValidateException.class})
    public R<String> validatorException(HttpServletRequest request, ValidateException e) {
        RequestInfoDTO reqInfo = getRequestInfo(request, true);
        log.error("参数检验错误，违反约束异常，异常信息为 {} {}", reqInfo, e.getMsg());
        return R.fail(ErrorCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), e.getMsg());
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    public R<String> methodArgumentNotValidExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
        RequestInfoDTO reqInfo = getRequestInfo(request);
        FieldError fieldError = e.getBindingResult().getFieldError();
        log.error("参数检验错误，方法参数无效异常，异常信息为 {} {}", reqInfo, fieldError.getDefaultMessage());
        return R.fail(ErrorCodeEnum.PARAMETER_VALIDATION_FAILED.getCode(), fieldError.getDefaultMessage());
    }

    @ExceptionHandler({ServiceException.class})
    public R<String> serviceException(HttpServletRequest request, ServiceException e) {
        RequestInfoDTO reqInfo = getRequestInfo(request);
        String uuid = getUUID();
        log.error("{} 服务异常 {}", uuid, reqInfo, e);
        return R.fail(ResultCode.FAIL.getCode(), e.getMsg());
    }

    private static RequestInfoDTO getRequestInfo(HttpServletRequest request) {
        return getRequestInfo(request, false);
    }

    private static RequestInfoDTO getRequestInfo(HttpServletRequest request, boolean withHeader) {
        Map<String, String> map = null;
        if (withHeader) {
            Enumeration<String> headerNames = request.getHeaderNames();
            map = new HashMap<>(16);

            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                map.put(name, request.getHeader(name));
            }
        }

        String params = "";
        if (Objects.equals(request.getMethod(), "GET")) {
            params = JsonUtils.toJson(request.getParameterMap());
        } else {
            params = (String) request.getAttribute("requestBodyMessage");
        }

        return RequestInfoDTO.builder().url(request.getRequestURL().toString()).params(params).ip(request.getRemoteAddr()).headerMap(map).build();
    }

    @Data
    @Builder
    private static class RequestInfoDTO {
        private String url;
        private Object params;
        private String ip;
        private Map<String, String> headerMap;
    }
}
